export const hashRE = /#.*$/
export const extRE = /\.(md|html)$/
export const endingSlashRE = /\/$/
export const outboundRE = /^[a-z]+:/i

export function normalize(path) {
    return decodeURI(path).replace(hashRE, '').replace(extRE, '')
}

export function getHash(path) {
    const match = path.match(hashRE)
    if (match) {
        return match[0]
    }
}

export function isExternal(path) {
    return outboundRE.test(path)
}

export function isMailto(path) {
    return /^mailto:/.test(path)
}

export function isTel(path) {
    return /^tel:/.test(path)
}

export function ensureExt(path) {
    if (isExternal(path)) {
        return path
    }
    const hashMatch = path.match(hashRE)
    const hash = hashMatch ? hashMatch[0] : ''
    const normalized = normalize(path)

    if (endingSlashRE.test(normalized)) {
        return path
    }
    return normalized + '.html' + hash
}

export function isActive(route, path) {
    const routeHash = decodeURIComponent(route.hash)
    const linkHash = getHash(path)
    if (linkHash && routeHash !== linkHash) {
        return false
    }
    const routePath = normalize(route.path)
    const pagePath = normalize(path)
    return routePath === pagePath
}

export function resolvePage(pages, rawPath, base) {
    if (isExternal(rawPath)) {
        return {
            type: 'external',
            path: rawPath
        }
    }
    if (base) {
        rawPath = resolvePath(rawPath, base)
    }
    const path = normalize(rawPath)
    for (let i = 0; i < pages.length; i++) {
        if (normalize(pages[i].regularPath) === path) {
            return Object.assign({}, pages[i], {
                type: 'page',
                path: ensureExt(pages[i].path)
            })
        }
    }
    console.error(`[vuepress] No matching page found for sidebar item "${rawPath}"`)
    return {}
}

function resolvePath(relative, base, append) {
    const firstChar = relative.charAt(0)
    if (firstChar === '/') {
        return relative
    }

    if (firstChar === '?' || firstChar === '#') {
        return base + relative
    }

    const stack = base.split('/')

    // remove trailing segment if:
    // - not appending
    // - appending to trailing slash (last segment is empty)
    if (!append || !stack[stack.length - 1]) {
        stack.pop()
    }

    // resolve relative path
    const segments = relative.replace(/^\//, '').split('/')
    for (let i = 0; i < segments.length; i++) {
        const segment = segments[i]
        if (segment === '..') {
            stack.pop()
        } else if (segment !== '.') {
            stack.push(segment)
        }
    }

    // ensure leading slash
    if (stack[0] !== '') {
        stack.unshift('')
    }

    return stack.join('/')
}

/**
 * @param { Page } page
 * @param { string } regularPath
 * @param { SiteData } site
 * @param { string } localePath
 * @returns { SidebarGroup }
 */
export function resolveSidebarItems(page, regularPath, site, localePath) {
    const { pages, themeConfig } = site

    const localeConfig = localePath && themeConfig.locales ? themeConfig.locales[localePath] || themeConfig : themeConfig

    const pageSidebarConfig = page.frontmatter.sidebar || localeConfig.sidebar || themeConfig.sidebar
    if (pageSidebarConfig === 'auto') {
        return resolveHeaders(page)
    }

    const sidebarConfig = localeConfig.sidebar || themeConfig.sidebar
    if (!sidebarConfig) {
        return []
    } else {
        const { base, config } = resolveMatchingConfig(regularPath, sidebarConfig)
        if (config === 'auto') {
            return resolveHeaders(page)
        }
        return config ? config.map(item => resolveItem(item, pages, base)) : []
    }
}

/**
 * @param { Page } page
 * @returns { SidebarGroup }
 */
function resolveHeaders(page) {
    const headers = groupHeaders(page.headers || [])
    return [
        {
            type: 'group',
            collapsable: false,
            title: page.title,
            path: null,
            children: headers.map(h => ({
                type: 'auto',
                title: h.title,
                basePath: page.path,
                path: page.path + '#' + h.slug,
                children: h.children || []
            }))
        }
    ]
}

export function groupHeaders(headers) {
    // group h3s under h2
    headers = headers.map(h => Object.assign({}, h))
    let lastH2
    headers.forEach(h => {
        if (h.level === 2) {
            lastH2 = h
        } else if (lastH2) {
            ;(lastH2.children || (lastH2.children = [])).push(h)
        }
    })
    return headers.filter(h => h.level === 2)
}

export function resolveNavLinkItem(linkItem) {
    return Object.assign(linkItem, {
        type: linkItem.items && linkItem.items.length ? 'links' : 'link'
    })
}

/**
 * @param { Route } route
 * @param { Array<string|string[]> | Array<SidebarGroup> | [link: string]: SidebarConfig } config
 * @returns { base: string, config: SidebarConfig }
 */
export function resolveMatchingConfig(regularPath, config) {
    if (Array.isArray(config)) {
        return {
            base: '/',
            config: config
        }
    }
    for (const base in config) {
        if (ensureEndingSlash(regularPath).indexOf(encodeURI(base)) === 0) {
            return {
                base,
                config: config[base]
            }
        }
    }
    return {}
}

function ensureEndingSlash(path) {
    return /(\.html|\/)$/.test(path) ? path : path + '/'
}

function resolveItem(item, pages, base, groupDepth = 1) {
    if (typeof item === 'string') {
        return resolvePage(pages, item, base)
    } else if (Array.isArray(item)) {
        return Object.assign(resolvePage(pages, item[0], base), {
            title: item[1]
        })
    } else {
        const children = item.children || []
        if (children.length === 0 && item.path) {
            return Object.assign(resolvePage(pages, item.path, base), {
                title: item.title
            })
        }
        return {
            type: 'group',
            path: item.path,
            title: item.title,
            sidebarDepth: item.sidebarDepth,
            children: children.map(child => resolveItem(child, pages, base, groupDepth + 1)),
            collapsable: item.collapsable !== false
        }
    }
}
